﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;

namespace TripleCComiC
{
    public partial class ListItem : UserControl
    {
        //
        // Structure, Class and Enumeration
        //
        [StructLayout(LayoutKind.Sequential)]
        public struct SHFILEINFO
        {
            public IntPtr hIcon;
            public IntPtr iIcon;
            public uint dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
            public string szTypeName;
        };
        public class TextChangingEventArgs : EventArgs
        {
            public string NewText { get; set; }
        }
        public enum listItemType : byte
        {
            /// <summary>
            /// Undefine file.
            /// </summary>
            File = 0x00,
            /// <summary>
            /// Image file.
            /// </summary>
            Image,
            /// <summary>
            /// Directory.
            /// </summary>
            Directory
        }

        //
        // Unmanaged method
        //
        [DllImport("shell32.dll")]
        public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);

        //
        // Parameter
        //
        private listItemType _type = listItemType.File;
        private string _item = String.Empty;
        private CancellationTokenSource _imageCancellation;
        public int num = 0; // For debug tracing.
        public bool IsItemSet
        {
            get
            {
                return !String.IsNullOrEmpty(_item);
            }
        }
        public Image Image
        {
            get
            {
                return pictureBox.Image;
            }
            set
            {
                int count = 0;
                while (_imageCancellation != null)
                {
                    int _num = num;
                    if (count > 100)
                    {
                        throw new TimeoutException("Set ListItem.Image timeout.");
                    }
                    _imageCancellation.Cancel();
                    Thread.Sleep(10);
                    count++;
                }
                pictureBox.Image = value;
            }
        }
        [Category("Others"),
        Description("Gets or sets the text contents of the label.")]
        public override string Text
        {
            get
            {
                return label.Text;
            }
            set
            {
                if (label.Text != value)
                {
                    try
                    {
                        TextChangingEventArgs args = new TextChangingEventArgs();
                        args.NewText = value;
                        this.OnTextChanging(args);
                        label.Text = value;
                    }
                    catch(Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }
        }
        [Category("Others"),
        Description("Set item.")]
        public string Item
        {
            get
            {
                return _item;
            }
            set
            {
                if (!String.IsNullOrEmpty(value))
                {
                    _item = value;
                    switch (_type)
                    {
                        case listItemType.Directory:
                            SetDirectoryIcon();
                            break;
                        case listItemType.File:
                            SetFileIcon();
                            break;
                        case listItemType.Image:
                            SetDefaultImage();
                            break;
                    }
                }
            }
        }
        [Category("Others"),
        Description("Select file type or directory.")]
        public listItemType Type
        {
            get
            {
                return _type;
            }
            set
            {
                _type = value;
                switch(_type)
                {
                    case listItemType.Directory:
                        SetDirectoryIcon();
                        break;
                    case listItemType.File:
                        SetFileIcon();
                        break;
                    case listItemType.Image:
                        SetDefaultImage();
                        break;
                }
            }
        }

        //
        // Constructor
        //
        public ListItem()
        {
            InitializeComponent();

            this.SetStyle(ControlStyles.Selectable, true);
            this.TabStop = true;

            //
            // Event
            //
            pictureBox.MouseDown += new MouseEventHandler(
                (object mouseEventSender, MouseEventArgs mouseEventArgs) => {
                    OnMouseDown(mouseEventArgs);
                });
            label.MouseDown += new MouseEventHandler(
                (object mouseEventSender, MouseEventArgs mouseEventArgs) =>
                {
                    OnMouseDown(mouseEventArgs);
                });
            pictureBox.DoubleClick += new EventHandler(
                (object eventSender, EventArgs eventArgs) =>
                {
                    OnDoubleClick(eventArgs);
                });
            label.DoubleClick += new EventHandler(
                (object eventSender, EventArgs eventArgs) =>
                {
                    OnDoubleClick(eventArgs);
                });
            label.Click += label_Click;
            textBox.LostFocus += textBox_LostFocus;
        }

        //
        // Event
        //
        /// <summary>
        /// Occurs before the Text property value changes.
        /// </summary>
        public event EventHandler<TextChangingEventArgs> TextChanging;
        public event EventHandler DoJob;

        protected override void OnMouseDown(MouseEventArgs e)
        {
            this.Focus();
            base.OnMouseDown(e);
        }

        protected override void OnEnter(EventArgs e)
        {
            this.Invalidate();
            base.OnEnter(e);
        }

        protected override void OnLeave(EventArgs e)
        {
            this.Invalidate();
            base.OnLeave(e);
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            base.OnPaint(pe);
            if (this.Focused)
            {
                var rc = this.ClientRectangle;
                rc.Inflate(-2, -2);
                ControlPaint.DrawFocusRectangle(pe.Graphics, rc);
            }
        }

        protected override void OnDoubleClick(EventArgs e)
        {
            OnDoJob(e);
            base.OnDoubleClick(e);
        }

        protected void OnTextChanging(TextChangingEventArgs e)
        {
            EventHandler<TextChangingEventArgs> handler = TextChanging;

            if (handler != null)
            {
                handler(this, e);
            }
        }
        
        protected void OnDoJob(EventArgs e)
        {
            EventHandler handler = DoJob;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        private void label_Click(object sender, EventArgs e)
        {
            if (!textBox.Visible)
            {
                textBox.Text = Text;
                textBox.Visible = true;
                textBox.Focus();
            }
        }

        private void textBox_LostFocus(object sender, EventArgs e)
        {
            if (textBox.Visible)
            {
                if (!String.IsNullOrWhiteSpace(textBox.Text)
                    && Text != textBox.Text)
                {
                    Text = textBox.Text;
                }
                textBox.Visible = false;
            }
        }

        //
        // Method
        //
        /// <summary>
        /// Get file's icon.
        /// </summary>
        /// <param name="fileName">Specific file's full path.</param>
        /// <returns>Icon data.</returns>
        private Icon GetIcon(string fileName)
        {
            Icon icon;
            try
            {
                IntPtr hImg; //the handle to the system image list
                SHFILEINFO shinfo = new SHFILEINFO();

                hImg = SHGetFileInfo(
                    fileName,
                    (uint)0x80,
                    ref shinfo,
                    (uint)Marshal.SizeOf(shinfo),
                    (uint)(0x100 | 0x400));

                icon = Icon.FromHandle(shinfo.hIcon);
            }
            catch (ArgumentException)
            {
                throw;
            }

            return icon;
        }

        /// <summary>
        /// Load image to memory.
        /// </summary>
        /// <param name="filePath">Image path.</param>
        /// <returns>Image data.</returns>
        private Image ImageFromFile(string fileName)
        {
            if (_imageCancellation != null)
            {
                _imageCancellation.Cancel();
            }
            if (!File.Exists(fileName))
            {
                throw new FileNotFoundException();
            }

            _imageCancellation = new CancellationTokenSource();
            
            using (FileStream fileStream = new FileStream(fileName, FileMode.Open))
            {
                byte[] bytes = new byte[fileStream.Length];
                int readBuffer = 0;
                while (readBuffer < bytes.Length)
                {
                    if (_imageCancellation.Token.IsCancellationRequested)
                    {
                        bytes = null;
                        _imageCancellation.Dispose();
                        _imageCancellation = null;
                        throw new Exception("Operations be cancelled.");
                    }
                    int nextBuffer = Math.Min(102400000, bytes.Length - readBuffer);

                    fileStream.Read(bytes, readBuffer, nextBuffer);
                    readBuffer += nextBuffer;
                }

                MemoryStream memoryStream = new MemoryStream(bytes);

                _imageCancellation.Dispose();
                _imageCancellation = null;

                if (memoryStream != null)
                {
                    int _num = num;
                    return Image.FromStream(memoryStream);
                }
                else
                {
                    throw new ArgumentNullException("Got null MemoryStream");
                }
            }

            throw new Exception("Unknown Error");
        }

        /// <summary>
        /// Resize image to thumbnail image.
        /// </summary>
        /// <param name="image">Original image data.</param>
        /// <returns>Thumbnail image.</returns>
        private Image ResizeImage(Image image)
        {

            double ratio =
                (image.Height * pictureBox.Width / image.Width > pictureBox.Width) ?
                1.0 * pictureBox.Height / image.Height :
                1.0 * pictureBox.Width / image.Width;

            ratio = (ratio > 1) ? 1 : ratio;
            return image.GetThumbnailImage(
                (int)(image.Width * ratio), (int)(image.Height * ratio),
                null, IntPtr.Zero);
        }

        /// <summary>
        /// Set Image as icon of directory.
        /// </summary>
        private void SetDirectoryIcon()
        {
            try
            {
                Image = Bitmap.FromHicon(GetIcon(_item).Handle);
            }
            catch
            {
                Image = Bitmap.FromHicon(
                    GetIcon(Environment.GetFolderPath(
                    Environment.SpecialFolder.CommonProgramFiles)).Handle);
            }
        }

        /// <summary>
        /// Set Image as icon of the file.
        /// </summary>
        private void SetFileIcon()
        {
            try
            {
                Image = Bitmap.FromHicon(
                    Icon.ExtractAssociatedIcon(_item).Handle);
            }
            catch
            {
                Image = Properties.Resources.TripleC;
            }
        }

        /// <summary>
        /// Set Image as default image.
        /// </summary>
        private void SetDefaultImage()
        {
            Image = Properties.Resources.TripleC;
        }

        /// <summary>
        /// Set Image as image which load from the file.
        /// </summary>
        public void SetImage()
        {
            if (_type == listItemType.Image)
            {
                try
                {
                    Image = ResizeImage(ImageFromFile(_item));
                }
                catch
                {
                    Image = Properties.Resources.TripleC;
                }
            }
        }

        /// <summary>
        /// Run DoJob.
        /// </summary>
        /// <param name="e"></param>
        public void RunJob(EventArgs e)
        {
            OnDoJob(e);
        }

        /// <summary>
        /// Show TextBox for change text.
        /// </summary>
        public void ChangeText()
        {
            label_Click(this, null);
        }
    }
}
